package com.jlf.core.server.bean;

import com.jlf.core.annotation.Domain;
import com.jlf.core.annotation.DomainRepository;
import com.jlf.core.annotation.AutoServer;
import com.jlf.core.annotation.ServerComponentScan;
import com.jlf.core.mp.persistence._SimplePersistence;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 用于扫码注册服务类的bean对象
 *
 * @author wujr
 * 2023/7/6
 * @version 1.0
 * 【变更历史】
 * [wujr 2023/7/6 1.0 新增]
 */
@Slf4j
@RequiredArgsConstructor
public class ServerBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    /**
     * 扫码注解：{@link AutoServer}的服务类，并注入为bean
     * @param beanFactory the bean factory used by the application context
     * @throws BeansException 注入失败，抛出异常
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        Map<String, Object> scanBeans = beanFactory.getBeansWithAnnotation(ServerComponentScan.class);
        if (scanBeans.isEmpty()){
            log.info("没有找到注解：ServerComponentScan，无法扫码Server的bean");
            return;
        }
        Set<String> basePackages = new HashSet<>();
        for (Map.Entry<String, Object> entry : scanBeans.entrySet()){
            Object bean = entry.getValue();
            ServerComponentScan annotation = AnnotationUtils.findAnnotation(bean.getClass(), ServerComponentScan.class);
            assert annotation != null;
            if (annotation.basePackages().length == 0){
                log.info("注解：ServerComponentScan，bean：{} 没有标注basePackages，无需扫码", entry.getKey());
                continue;
            }
            basePackages.addAll(Arrays.asList(annotation.basePackages()));
        }
        if (basePackages.isEmpty()){
            log.info("被注解：ServerComponentScan 标记的bean，该注解都没有标注basePackages，无法扫码Server的bean");
            return;
        }
        ClassPool classPool = ClassPool.getDefault();
        for (String basePackage : basePackages){
            scanServer(classPool, basePackage, beanFactory);
        }
    }
    protected void scanServer(ClassPool classPool, String basePackage, ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Reflections reflections = new Reflections(basePackage);
        Set<Class<?>> set = reflections.getTypesAnnotatedWith(AutoServer.class);
        DefaultListableBeanFactory factory = (DefaultListableBeanFactory)beanFactory;
        for (Class<?> clz : set){
            AutoServer annotation = clz.getAnnotation(AutoServer.class);
            Class<?> serverBeanClass = buildServerBeanClass(beanFactory, classPool, clz, annotation);

            GenericBeanDefinition bd = new GenericBeanDefinition();
            bd.setBeanClass(serverBeanClass);
            Domain domainAnnotation = annotation.domain().getAnnotation(Domain.class);
            String beanName;
            if (domainAnnotation != null){
                beanName = domainAnnotation.code() + "Server";
            }else {
                beanName = DomainRepository.domainCode(annotation.domain());
            }
            factory.registerBeanDefinition(beanName, bd);
        }
    }
    @SuppressWarnings("rawtypes")
    protected _SimplePersistence<?> findPersistence(ConfigurableListableBeanFactory beanFactory, Class<?> domainClass){
        Map<String, _SimplePersistence> beans = beanFactory.getBeansOfType(_SimplePersistence.class);
        for (Map.Entry<String, _SimplePersistence> entry : beans.entrySet()){
            _SimplePersistence bean = entry.getValue();
            ParameterizedType parameterizedType = (ParameterizedType)bean.getClass().getGenericSuperclass();
            String typeName = parameterizedType.getActualTypeArguments()[1].getTypeName();
            if (typeName.equals(domainClass.getName())){
                return bean;
            }
        }
        return null;
    }
    protected Class<?> buildServerBeanClass(ConfigurableListableBeanFactory beanFactory,
                                            ClassPool classPool, Class<?> clz, AutoServer annotation) throws BeansException{
        String className = clz.getName() + "$$Proxy";
        CtClass cc = classPool.makeClass(className);

        try {
            CtClass ccServer = classPool.getCtClass(clz.getName());
            cc.setSuperclass(ccServer);
            CtConstructor[] constructors = ccServer.getConstructors();
            // 构建构造函数
            if (constructors != null) {
                for (CtConstructor constructor : constructors) {
                    CtClass[] parameterTypes = constructor.getParameterTypes();
                    CtConstructor constructor1 = new CtConstructor(parameterTypes, cc);
                    constructor1.setBody("{super($$);}");
                    cc.addConstructor(constructor1);
                }
            }
            ClassFile ccFile = cc.getClassFile();
            ConstPool constPool = ccFile.getConstPool();
            AnnotationsAttribute attr = buildAnnotation(constPool, "org.springframework.beans.factory.annotation.Autowired");

            String txManagerFieldName = getFieldNameByType(clz, DataSourceTransactionManager.class);
            if (txManagerFieldName == null) {
                txManagerFieldName = "dataSourceTransactionManager";
                CtField cfTxManager = CtField.make("private org.springframework.jdbc.datasource.DataSourceTransactionManager dataSourceTransactionManager;", cc);
                cc.addField(cfTxManager);
                cfTxManager.getFieldInfo().addAttribute(attr);
            }

            String txDefFieldName = getFieldNameByType(clz, TransactionDefinition.class);
            if (txDefFieldName == null) {
                txDefFieldName = "transactionDefinition";
                CtField cfTxDef = CtField.make("private org.springframework.transaction.TransactionDefinition transactionDefinition;", cc);
                cc.addField(cfTxDef);
                cfTxDef.getFieldInfo().addAttribute(attr);
            }
            CtField cfPersistence;
            try {
                cfPersistence = ccServer.getDeclaredField("persistence");
            } catch (NotFoundException e) {
                // 该字段不存在，注入
                _SimplePersistence<?> persistenceBean = findPersistence(beanFactory, annotation.domain());
                if (persistenceBean == null) {
                    log.info("构建服务：{} 的代理bean，领域：{} 的持久层bean没有找到", clz.getName(), annotation.domain().getSimpleName());
                    throw new BeanInstantiationException(clz, "构建服务bean类异常");
                }

                cfPersistence = CtField.make("private " + persistenceBean.getClass().getName() + " persistence;", cc);
                cc.addField(cfPersistence);
                cfPersistence.getFieldInfo().addAttribute(attr);
            }

            String cfProxy = "private com.jlf.core.server._SimpleServer _proxy;";
            CtField fProxy = CtField.make(cfProxy, cc);
            cc.addField(fProxy);
            CtMethod cmInit = new CtMethod(CtClass.voidType, "_initConstructor", null, cc);
            cmInit.setModifiers(Modifier.PUBLIC);
            cmInit.setBody("{this._proxy = new com.jlf.core.server._SimpleServer($class, this, this.persistence, this." + txManagerFieldName + ", this." + txDefFieldName + ");}");
            cc.addMethod(cmInit);
            attr = buildAnnotation(constPool, "javax.annotation.PostConstruct");
            cmInit.getMethodInfo().addAttribute(attr);

            String sbAdd = "public " + annotation.domain().getName() + " add(" +
                    annotation.domain().getName() + " record) throws Exception{" +
                    "return (" + annotation.domain().getName() + ")_proxy.add(record);}";
            CtMethod method = CtMethod.make(sbAdd, cc);
            cc.addMethod(method);
            if (!Object.class.equals(annotation.addWrapper())) {
                // 新增需要封装
                sbAdd = "public void add(" +
                        annotation.addWrapper().getName() + " record) throws Exception{" +
                        "_proxy.add(record);}";
                method = CtMethod.make(sbAdd, cc);
                cc.addMethod(method);
            }
            cc.writeFile();
            return cc.toClass(clz);
        }catch(BeanInstantiationException e){
            throw e;
        }catch (Exception e){
            log.error("构建服务：{} 的代理bean异常：", clz.getName(), e);
            throw new BeanInstantiationException(clz, "构建服务bean类异常");
        }
    }
    private String getFieldNameByType(Class<?> clz, Class<?> type){
        Field[] fields = clz.getDeclaredFields();
        for (Field field : fields){
            if (type.equals(field.getType())){
                return field.getName();
            }
        }
        return null;
    }
    private AnnotationsAttribute buildAnnotation(ConstPool constPool, String annotation){
        AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation annot = new Annotation(annotation, constPool);
        attr.addAnnotation(annot);
        return attr;
    }
}
